#include "../code/Server.hpp"

// 管理Mime映射和Statu映射的两张哈希表

unordered_map<std::string, std::string> Mime = {
{".aac",        "audio/aac"},
{".abw",        "application/x-abiword"},
{".arc",        "application/x-freearc"},
{".avi",        "video/x-msvideo"},
{".azw",        "application/vnd.amazon.ebook"},
{".bin",        "application/octet-stream"},
{".bmp",        "image/bmp"},
{".bz",         "application/x-bzip"},
{".bz2",        "application/x-bzip2"},
{".csh",        "application/x-csh"},
{".css",        "text/css"},
{".csv",        "text/csv"},
{".doc",        "application/msword"},
{".docx",       "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
{".eot",        "application/vnd.ms-fontobject"},
{".epub",       "application/epub+zip"},
{".gif",        "image/gif"},
{".htm",        "text/html"},
{".html",       "text/html"},
{".ico",        "image/vnd.microsoft.icon"},
{".ics",        "text/calendar"},
{".jar",        "application/java-archive"},
{".jpeg",       "image/jpeg"},
{".jpg",        "image/jpeg"},
{".js",         "text/javascript"},
{".json",       "application/json"},
{".jsonld",     "application/ld+json"},
{".mid",        "audio/midi"},
{".midi",       "audio/x-midi"},
{".mjs",        "text/javascript"},
{".mp3",        "audio/mpeg"},
{".mpeg",       "video/mpeg"},
{".mpkg",       "application/vnd.apple.installer+xml"},
{".odp",        "application/vnd.oasis.opendocument.presentation"},
{".ods",        "application/vnd.oasis.opendocument.spreadsheet"},
{".odt",        "application/vnd.oasis.opendocument.text"},
{".oga",        "audio/ogg"},
{".ogv",        "video/ogg"},
{".ogx",        "application/ogg"},
{".otf",        "font/otf"},
{".png",        "image/png"},
{".pdf",        "application/pdf"},
{".ppt",        "application/vnd.ms-powerpoint"},
{".pptx",       "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
{".rar",        "application/x-rar-compressed"},
{".rtf",        "application/rtf"},
{".sh",         "application/x-sh"},
{".svg",        "image/svg+xml"},
{".swf",        "application/x-shockwave-flash"},
{".tar",        "application/x-tar"},
{".tif",        "image/tiff"},
{".tiff",       "image/tiff"},
{".ttf",        "font/ttf"},
{".txt",        "text/plain"},
{".vsd",        "application/vnd.visio"},
{".wav",        "audio/wav"},
{".weba",       "audio/webm"},
{".webm",       "video/webm"},
{".webp",       "image/webp"},
{".woff",       "font/woff"},
{".woff2",      "font/woff2"},
{".xhtml",      "application/xhtml+xml"},
{".xls",        "application/vnd.ms-excel"},
{".xlsx",       "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
{".xml",        "application/xml"},
{".xul",        "application/vnd.mozilla.xul+xml"},
{".zip",        "application/zip"},
{".3gp",        "video/3gpp"},
{".3g2",        "video/3gpp2"},
{".7z",         "application/x-7z-compressed"}
};


unordered_map<std::string, std::string> Statu = 
{
{100,  "Continue"},
{101,  "Switching Protocol"},
{102,  "Processing"},
{103,  "Early Hints"},
{200,  "OK"},
{201,  "Created"},
{202,  "Accepted"},
{203,  "Non-Authoritative Information"},
{204,  "No Content"},
{205,  "Reset Content"},
{206,  "Partial Content"},
{207,  "Multi-Status"},
{208,  "Already Reported"},
{226,  "IM Used"},
{300,  "Multiple Choice"},
{301,  "Moved Permanently"},
{302,  "Found"},
{303,  "See Other"},
{304,  "Not Modified"},
{305,  "Use Proxy"},
{306,  "unused"},
{307,  "Temporary Redirect"},
{308,  "Permanent Redirect"},
{400,  "Bad Request"},
{401,  "Unauthorized"},
{402,  "Payment Required"},
{403,  "Forbidden"},
{404,  "Not Found"},
{405,  "Method Not Allowed"},
{406,  "Not Acceptable"},
{407,  "Proxy Authentication Required"},
{408,  "Request Timeout"},
{409,  "Conflict"},
{410,  "Gone"},
{411,  "Length Required"},
{412,  "Precondition Failed"},
{413,  "Payload Too Large"},
{414,  "URI Too Long"},
{415,  "Unsupported Media Type"},
{416,  "Range Not Satisfiable"},
{417,  "Expectation Failed"},
{418,  "I'm a teapot"},
{421,  "Misdirected Request"},
{422,  "Unprocessable Entity"},
{423,  "Locked"},
{424,  "Failed Dependency"},
{425,  "Too Early"},
{426,  "Upgrade Required"},
{428,  "Precondition Required"},
{429,  "Too Many Requests"},
{431,  "Request Header Fields Too Large"},
{451,  "Unavailable For Legal Reasons"},
{501,  "Not Implemented"},
{502,  "Bad Gateway"},
{503,  "Service Unavailable"},
{504,  "Gateway Timeout"},
{505,  "HTTP Version Not Supported"},
{506,  "Variant Also Negotiates"},
{507,  "Insufficient Storage"},
{508,  "Loop Detected"},
{510,  "Not Extended"},
{511,  "Network Authentication Required"}
};

// 工具类用于实现一些小工具接口的实现
class Util
{
public:
    // 将字符串形式表示的十六进制数(0~9, a~z, A~Z)转为真正的数字的十六进制数
    static char HEXTOI(char c)
    {
        if(c >= '0' && c <= '9')
        {
            return c-'0';
        }

        if(c >= 'a' && c <= 'z')
        {
            // 一定要对最后的结果加10，不然就是相对于字符'a'的offset
            return c - 'a' + 10; 
        }

        if(c >= 'A' && c <= 'Z')
        {
            return c - 'A' + 10;
        }

        return -1;
    }
    // 字符串分割函数：将指定的字符串按照指定的字符切割放入数组中最终返回子串的数量
    static size_t Split(const std::string &src, const std::string &sep, std::vector<std::string> *arry)
    {
        
    } 

    // 向指定文件名中读取数据
    static bool ReadFile(const std::string &path, std::string *buffer)
    {
        std::ifstream ifs;
        ifs.open(path.c_str(), std::fstream::binary);
        if(!ifs.is_open())
        {
            return false;
        }

        size_t fsize = 0;

        // seekg()设置读取位置，我们将读取位置设置到文件的结尾
        ifs.seekg(0, ifs.end());

        // tellg() 返回当前文件读取指针的位置
        fsize = ifs.tellg();

        // 跳转到文件的起始位置
        ifs.seekg(0, ifs.beg());

        // 我们这里采用手动扩容，不自动扩容防止空间浪费
        buffer->rsize(fsize);

        // istream& read (char* s, streamsize n);
        // 这里第一个参数不适用buffer->c_str()，read的参数需要的是char*的类型，c_str()返回的是const char*
        ifs.read(&(*buffer[0]), fsize);
        if(ifs.good())
        {
            ifs.close();
            return false;
        }

        ifs.close();
        return true;
    }


    // 向指定文件名中写入数据
    static bool WriteFile(const std::string &path, const std::string &buffer)
    {
        std::ofstream ofs;
        // 采用二进制方式打开以及覆盖写入的方式来写入
        ofs.open(path.c_str(), std::binary | std::trunc);

        if(ofs.is_open() == false)
        {
            return false;
        }

        // ostream& write (const char* s, streamsize n);
        ofs.wtite(buffer.c_str(), buffer.size());

        if(ofs.good() == false)
        {
            ofs.close();
            return false;
        }

        ofs.close();
        return true;
    }

    // Url编码
    // Url编码避免Url中的资源路径的查询字符串中的特殊字符和Http请求中的特殊字符产生歧义
    // 编码规则：将特殊字符的十六进制数字改为两位字符并且在前面加上% 例如：C++ -> C%2b%2b 
    //          绝对不编码的几个字符 . - _ 字母和数字
    // W3C规则查询字符串中的空格必须变为加号
    static std::string UrlEncode(std::string &url, bool convert_space_to_plus)
    {
        std::string ret;
        for(auto &e : url)
        {
            // 情况1. 四个特殊字符
            if()

            // 情况2. 空格转加号
            if(e == ' ' && convert_space_to_plus == true)
            {
                ret += '+';
                continue;
            }

            // 情况3. 字母或者数字
            // isalnum() the system call checks for an alphanumeric character; it is equivalent to (isalpha(c) || isdigit(c)).
            if(isalnum(e))
            {
                res += e;
                continue;
            }

            // 除上述三种情况其他的情况都是转成特定的编码格式
            char tmp[4] = {0};
            // 格式化字符串将其放入到指定的一块地址空间
            snprintf(tmp, 4, "%%%02x", e);
            res += tmp;
        }

        return res;
    }

    // Url解码
    // 解码规则：
    // 遇到了%就将百分号后面的两个字符转为16进制的数字，再转为10进制的数值得到ASCII码值，转为字符
    static std::string UrlDecode(std::string &url, bool convert_plus_to_space)
    {
        std::string ret;
        for(int i = 0; i<url.size(); i++)
        {
            if(url[i] == '+' && convert_plus_to_space == true)
            {
                ret += ' ';
            }

            // 防止后面的两个字符的访问越界
            if(url[i] == '%' && i<url.size()-2)
            {
                // 先将后面的两个字符转化为十六进制的数字
                char v1 = HEXTOI(url[i+1]);
                char v2 = HEXTOI(url[i+2]);

                // 将两个十六进制数字相加得到ASCII值
                char tmp = v1*16 + v2;
                ret += tmp;
                i+=2;
                continue;
            }

            // 除以上两种情况的其他情况
            ret += url[i];
        }

        return ret;
    }   

    // 响应状态码与其描述信息的映射
    static std::string GetStatu(std::string &code)
    {
        auto it = Statu.find(code);
        if(it == Statu.end())
        {
            return "Illegalit code try again!";
        }

        return it->second;
    }

    // 文件后缀名和文件类型的映射
    static std::string GetFileType(std::string &suffix)
    {
        auto it = Mime.find(suffix);
        if(it == Mime.end())
        {
            return "Unknow suffix try again";
        }

        return it->second;
    }

    // 判断一个文件是否为目录
    static bool IsDir(std::string &filename)
    {
        struct stat statbuf;
        int ret = stat(filename.c_str(), &st);

        if(ret < 0)
        {
            return false;
        }

        // 调用宏来判断是不是一个目录
        return S_ISDIR(st.st_mode);
    }

    // 判断一个文件是否为普通文件
    static bool IsFile(std::string &filename)
    {
        struct stat statbuf;
        int ret = stat(filename.c_str(), &st);

        if(ret < 0)
        {
            return false;
        }

        // 调用宏来判断是不是一个普通文件
        return S_ISREG(st.st_mode);
    }

    // 判断Http的请求资源路径是否合法
    // 根据Linux的目录结构计算目录深度来判断路径的合法性
    // 判断方法：按照 / 为分隔符来判断..就减一除此之外就加一
    static bool IsLegalPath(std::string &path)
    {
        int level = 0;
        std::vector<std::string> subdir;
        Split(path, '/', &subdir);

        for(auto &s : subdir)
        {
            if(level <= 0)
            {
                return false;
            }

            if(s == "..")
            {
                level--:
                break;
            }

            level++;
        }

        return true;
    }

};


// Http请求：设计思想将所有的成员和接口公有化便于操作
class HttpRequest
{
public:
    // 重置上下文
    void ReSet()
    {
        _method.clear();
        _version.clear();
        _url.clear();
        _findstr.clear();
        _headers.clear();
        _params.clear();

        // std::smatch没有提供clear()接口使用空对象交换的方式来清空一个smatch对象
        std::smacth match;
        _macthes.swap(match);
    }

    // 设置头部字段
    void SetHeader(std::string &key, std::string &value)
    {
        _headers.insert(std::make_pair(key, value));
    }

    // 查看是否存在一个头部字段
    bool HasHeader(std::string &key)
    {
        auto it = _headers.find(key);
        if(it == _headers.end())
        {
            return false;
        }

        return true;
    }

    // 获取一个头部字段对应的value值
    std::string GetHeader(std::string &key)
    {
        auto it = _headers.find(key);
        if(it == _headers.end())
        {
            return " ";
        }

        return it->second;
    }

    // 设置查询字符串
    void SetParam(std::string &key, std::string &value)
    {
        _params.insert(std::make_pair(key, value));
    }

    // 查看是否存在一个查询字符串
    bool HasHeader(std::string &key)
    {
        auto it = _params.find(key);
        if(it == _params.end())
        {
            return false;
        }

        return true;
    }

    // 获取一个查询字符串对应的value值
    std::string GetParam(std::string &key)
    {
        auto it =  _params.find(key);
        if(it ==  _params.end())
        {
            return " ";
        }

        return it->second;
    }

    // 获取正文的长度
    size_t GetContentLength()
    {
        // Content-Length: 1234\r\n
        auto it = _headers.find("Content-Length");
        if(it == _headers.end())
        {
            return 0;
        }

        std::string strlen = GetHeader("Content-Length");
        return std::stol(strlen);
    }

    // 判断一个连接是否为短连接
    bool Close()
    {
        // 没有Connection字段，或者有Connection字段但是Connection字段的值为close则为短链接，值为keep-alive为长连接
        if(HasHeader("Connecion") == true && GetHeader("Connection") == "keep-alive")
        {
            return false;
        }

        return true;
    }

public:
    // 请求方法
    std::string _method;

    // Http版本
    std::string _version;

    // 请求资源定位符
    std::string _url;

    // 请求查询字符串
    std::string _findstr;

    // 头部信息
    std::unordered_map<std::string, std::string> _headers;

    // 正则表达式
    std::smacth _macthes;

    // 查询字符串
    std::unordered_map<std::string, std::string> _params;
};

// Http响应
class Response
{
public:
    Response()
    :_statu(200)
    ,_redirect_flag(false)
    {}
    
    Response(int statu)
    :_statu(statu)
    ,_redirect_flag(false)
    {}

    // 对内容进行重置
    void Reset()
    {
        // 把相关的字段设置为初始字段或者清空
        _statu = 200;
        _redirect_flag = false;
        _headers.clear();
        _body.clear();
        _path.clear();
    }

    // 新增头部字段
    bool SetHeader(std::string &key, std::string &value)
    {
        return _headers.insert(std::make_pair(key, value));
    }

    // 查找头部字段
    bool HasHeader(std::string &key)
    {
        auto it = _headers.find(key);
        if(it == _headers.end())
        {
            return false;
        }

        return true;
    }

    // 获取头部字段
    std::string GetHeader(std::string &key)
    {
        // 不存在就返回空
        if(HasHeader(key) == false)
        {
            return "";
        }

        return _headers[key];
    }

    // 设置正文的内容和正文的类型
    void SetContent(std::string &body, std::string &type = "text/html")
    {
        // 1.将body设置进成员变量
        _body = body;

        // 2.设置头部字段
        SetHeader("Content-tyoe: ", type);
    }

    // 重定向的设置(重定向的种类不同，对应的状态码也不同)
    void SetRedirect(std::string &path, int statu = 302)
    {
        _statu = statu;
        _path = path;
    }

    // 是否关闭连接(长连接不关闭，短链接关闭)
    bool Close()
    {
        // 没有Connection字段，或者有Connection字段但是Connection字段的值为close则为短链接，值为keep-alive为长连接
        if(HasHeader("Connecion") == true && GetHeader("Connection") == "keep-alive")
        {
            return false;
        }

        return true;
    }

public:
    // 响应状态码
    int _statu;

    // 头部字段
    std::unordered_map<std::string, std::string> _headers;

    // 响应正文
    std::string _body;

    // 是否进行了重定向
    bool _redirect_flag;

    // 重定向后的资源路径
    std::string _path;
};

// Http上下文内容，该模块记录Http请求的接收和处理进度
// 模块存在的意义：有可能出现缓冲区里面的数据并不是一条完整的Http请求，
//                一条请求的处理可能要多次接收到数据后才可以完成处理，因此我们要记录接收请求的进度和处理请求的进度

// Http请求行的最大长度限制 8kb
#define MAX_LINE 8192

// 自定义枚举类型：值为当前接收及解析的状态
typedef enum
{
    // 接收处理Http请求错误
    RECV_HTTP_ERROR,
    
    // 接收处理Http请求行
    RECV_HTTP_LINE,

    // 接收处理Http请求头部
    RECV_HTTP_HEAD,

    // 接收处理Http请求正文
    RECV_HTTP_BODY,

    // 接收处理Http请求完毕
    RECV_HTTP_OVER
}HttpRecvStatu;

class HttpContent
{
private:
    bool ParseHttpLine(std::string &line)
    {
        // HTTP请求的格式
        // "GET /www.baidu.com/login?usrname=zhangsan&pwd=123123 HTTP/1.1"
        // "GET /www.baidu.com/login?usrname=zhangsan&pwd=123123 HTTP/1.1\n"
        // "GET /www.baidu.com/login?usrname=zhangsan&pwd=123123 HTTP/1.1\r\n"
        // std::string str = "GET /www.baidu.com/login HTTP/1.1\r\n";
        // 获取请求方法 + 资源路径 + 查询字符串(可能存在也可能不存在) + 版本 + 分隔符
        std::regex e("(GET|PUT|DELET|POST|HEAD) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\n|\r\n)?"); 
        // ()中是要匹配提取的内容
        // [^?]表示匹配提取非问号字符
        // [^?]后面的*表示提取0次或者n次因为可能GET后面可能没有要访问的对应资源
        // \\?表示原始的?字符，?字符是个特殊的字符，用两个\就可以表示原始的?字符
        // (HTTP/1\\.[01])表示以HTTP/1.开始的后面跟了一个0或者1的字符串
        // (?:\n|\r\n)? 括号里面的?:表示要匹配的字符串但是不捕捉，括号外面的表示匹配一次或者0次

        std::smatch matches;
        bool ret = std::regex_match(line, matches, e);

        if(ret == false)
        {
            _recv_statu = RECV_HTTP_ERROR;
            _resp_statu = 400; // {400,  "Bad Request"}
            return false;
        }
        // 0 GET /www.baidu.com/login HTTP/1.1
        // 1 GET
        // 2 /www.baidu.com/login
        // 3 
        // 4 HTTP/1.1
        _request._method = matches[1];
        // 对url进行解码，但是不用启用空格转为加号的规则
        _request._path = Util::UrlDecode(matches[2], false);
        _request._version = matches[4];
        std::vector<std::string> query_string_arry;
        std::string query_line = matches[3];

        Util::Split(query_line, "&", &query_string_arry);
        for(auto &str : query_string_arry)
        {
            // 查询字符串中的键值对是以=为分隔符进行分隔的
            size_t pos = str.find("=");
            if(pos = std::string::npos)
            {
                // 非法的一个查询字符串
                _recv_statu = RECV_HTTP_ERROR;
                _resp_statu = 400; // {400,  "Bad Request"}

                return false;
            }

            // 左闭右开+舍弃等号
            std::string key = Util::UrlDecode(str.substr(0, pos), true);
            std::string value = Util::UrlDecode(str.substr(pos+1), true);
            _request.SetParam(key, value);
        }

        return true;
    }


    // 从一个连接(Connection)中的缓冲区中读取Http请求行
    bool RecvHttpLine(Buffer *buf)
    {
        // 1.从缓冲区中获取一行数据
        std::string line = buf->Getline();

        // 2.需要考虑两种情况：2.1.缓冲区的数据不是一个完整的Http请求行，2.2.请求行的数据大小超过8kb
        //      对于上述的第二种情况可能会出现两种不一样的情况：
        //       2.2.1.第一则是数据太大太大了我们甚至没有从缓冲区里面读取到了一条完整的请求行，
        //       2.2.2.第二则是我们读取到了一条完整的请求行但是该请求行的长度过长超过了8kb
        // 我们根据不同的情况来判断会出现什么样的结果
        if(line.size() == 0)
        {
            // 缓冲区里面用数据但是太大了对应情况2.2.1.
            if(buf->ReadAbleSize() > 0)
            {
                _recv_statu = RECV_HTTP_ERROR;
                _resp_statu = 414; // {414,  "URI Too Long"}
                return false;
            }

            return true;
        }

        // 走到这里代表了读到了一条完整的Http请求行
        if(line.size() > MAX_LINE)
        {
            _recv_statu = RECV_HTTP_ERROR;
            _resp_statu = 414; // {414,  "URI Too Long"}
            return false;
        }

        // 读取到了请求行之后要对请求行进行解析
        bool ret = ParseHttpLine(line);

        if(ret == false)
        {
            return false;
        }

        buf->MoveReadPos(line.size());

        return true;
    }

    // 解析Http请求头部
    bool ParseHttpHead(std::string &line)
    {
        // key: val\r\nkey: val\r\n....
        // 末尾是换行就要去掉换行符
        if(line.back() == "\n")
        {
            line.pop_back();
        }

        // 末尾是回车就去掉回车
        if(line.back() == "\r")
        {
            line.pop_back();
        }

        size_t pos = line.find(": ");
        if(pos == std::string::npos)
        {
            _recv_statu = RECV_HTTP_ERROR;
            _resp_statu = 400; // 错误请求
            return false;
        }

        // 左闭右开，去掉": "
        std::string key = line.substr(0,pos);
        std::string value = line.substr(pos+2);

        _request.SetParam(key, value);

        return true;
    }

    // 接收Http请求头部
    bool RecvHttpHead(BUffer *buf)
    {
        // 一行一行的读取数据直到遇到\r\n或者\n为止
        while(1)
        {
            std::string line = buf->GetLineAndPop();
            if(line == "\r\n" || line == "\n")
            {
                break;
            }

            ParseHttpHead(line);
        }

        _recv_statu = RECV_HTTP_HEAD;
        return true;
    }

    // 从缓冲区中读取Http正文
    bool RecvHttpBody(Buffer *buf)
    {
        // 1.从request中的头部字段获取正文的长度
        size_t content_length = _request.GetContentLength();
        
        // 如果从头部字段获取到的正文长度为0，则证明这条Http请求处理完成直接放回
        if(content_length == 0)
        {
            _recv_statu = RECV_HTTP_OVER;
            _resp_statu = 200; // {200, "OK"}
            return true;
        }

        // 2.获取实际接收的正文长度和还要接收的正文长度(因为我们调用该接口可能是第一次调用后没有读取到完整的Http正文，后续接着调用的)
        size_t body_length = _request._body.size();
        size_t real_size = content_length - body_length;

        // 3.从缓冲区里面获取Http正文但是要考虑两种情况:
        //   3.1.缓冲区里面的数据有一个完整的Http正文
        //   3.2.缓冲区里面的数据没有一个完整的Http正文
        if(real_size <= buf->ReadAbleSize())
        {
            // 追加写入到_request的正文中
           _request._body.append(buf->ReadPosition(), real_len);
            buf->MoveReadOffset(real_len);
            _recv_statu = RECV_HTTP_OVER;
            _resp_statu = 200; //{200, "OK"}

            return true;
        }

        // 针对3.2这种情况我们将buf中的数据取出来追加到_request._body中并且不设置状态码
        _request._body.append(buf->ReadPosition(), buf->ReadAbleSize());
        buf->MoveReadOffset(buf->ReadAbleSize);

        return true;
    }

public:
    HttpContent()
    :_resp_statu(200)
    ,_recv_statu(RECV_HTTP_LINE)
    {}

    // 获取_resp_statu
    int RespStatu()
    {
        return _resp_statu;
    }

    // 获取当前接收及解析的状态
    HttpRecvStatu RecvStatu()
    {
        return _recv_statu;
    }

    // 获取Http请求
    HttpRequest &Request()
    {
        return _request;
    }

    // 获取Http请求
    void RecvHttpRequest()
    {
        // switch-case每个case后面不加break;
        // 因为处理完请求行要处理请求头部，接着处理请求正文
        switch(_recv_statu)
        {
            case RECV_HTTP_LINE:
                RecvHttpLine();
            case RECV_HTTP_HEAD:
                RecvHttpHead();
            case RECV_HTTP_BODY:
                RecvHttpBody();
                break;
            default:
                break;            
        }
    }

private:
    // 响应的状态码
    int _resp_statu;

    // 当前接收及解析的状态
    HttpRecvStatu _recv_statu;

    // 已经解析得到的信息
    HttpRequest _request;
};

